package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    // TODO: 2022/2/6
    //可以使用异步编排
    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);

        goods.setId(skuId);
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setCreateTime(new Date());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());

        //获取品牌数据
        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
        goods.setTmId(trademark.getId());
        goods.setTmLogoUrl(trademark.getLogoUrl());
        goods.setTmName(trademark.getTmName());

        //获取分类数据
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory3Name(categoryView.getCategory3Name());

        //平台属性对象集合
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
        List<SearchAttr> searchAttrList = new ArrayList<>();
        for (BaseAttrInfo baseAttrInfo : attrList) {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性id赋值
            searchAttr.setAttrId(baseAttrInfo.getId());
            //平台属性名赋值
            searchAttr.setAttrName(baseAttrInfo.getAttrName());
            //平台属性值名赋值,只有一个
            searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            searchAttrList.add(searchAttr);
        }

        goods.setAttrs(searchAttrList);

        goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    @Override
    public void updateHotScore(Long skuId) {
        String hotScoreKey = "hotScore";
        Double count = redisTemplate.opsForZSet().incrementScore(hotScoreKey, skuId, 1);
        if(count%10 == 0){
            //更新es
            Optional<Goods> optional = goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        /**
         *  //品牌 此时vo对象中的id字段保留（不用写） name就是“品牌” value: [{id:100,name:华为,logo:xxx},{id:101,name:小米,log:yyy}]
         *     private List<SearchResponseTmVo> trademarkList;
         *     private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
         *     private List<Goods> goodsList = new ArrayList<>();
         *     private Long total;//总记录数
         *     在一个方法中赋值
         *
         *
         *     private Integer pageSize;//每页显示的内容
         *     private Integer pageNo;//当前页面
         *     private Long totalPages;
         */
        //编写一个方法，将dsl语句写入searchRequest对象中
        SearchRequest searchRequest = this.searchRequestDsl(searchParam);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //数据集转换 将searchResponse转化成searchResponseVo
        //这个方法中给前四个属性赋值
        SearchResponseVo searchResponseVo = this.searchResult(searchResponse);
        //每页显示大写
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //当前页
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //总页数
        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageSize() - 1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;
    }

    //返回数据集对象
    private SearchResponseVo searchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        List<Goods> goodsList = new ArrayList<>();

        //设置总记录数
        searchResponseVo.setTotal(searchResponse.getHits().getTotalHits().value);

        //设置检索出来的数据
        SearchHit[] hits = searchResponse.getHits().getHits();
        if(hits!=null && hits.length>0){
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //判断是否有高亮字段，如果有高亮字段，取高亮字段的title
                if(hit.getHighlightFields().get("title")!=null){
                    Text title = hit.getHighlightFields().get("title").getFragments()[0];
                    goods.setTitle(title.toString());
                }
                goodsList.add(goods);
            }
        }
        searchResponseVo.setGoodsList(goodsList);
        //赋值品牌数据 从聚合中获取
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //获取品牌数据
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //设置品牌id
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));
            //设置品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //设置品牌logo
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);

            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //赋值平台属性
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        //获取key给平台属性对象赋值
        List<SearchResponseAttrVo> attrList = attrIdAgg.getBuckets().stream().map(bucket -> {
            //创建一个对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //获取平台属性id
            Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            searchResponseAttrVo.setAttrId(keyAsNumber.longValue());
            //获取平台属性名
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);
            //获取属性值名称
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueNameList);
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        searchResponseVo.setAttrsList(attrList);

        return searchResponseVo;
    }

    //创建SearchRequest对象
    private SearchRequest searchRequestDsl(SearchParam searchParam) {
        /*官方文档
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchRequest.source(searchSourceBuilder);

         */

        /*dsl语句
        GET goods/_search
        {
          "query": {
            "bool": {
              "must": [
                {
                  "match": {
                    "title": {
                      "query": "小米手机",
                      "operator": "or"
                    }
                  }
                }
              ]
            }
          }
        }
         */
        //构建一个查询器,相当于dsl语句中的{}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//相当于{query -- bool}

        //判断条件是否存在
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            //说明是通过关键词检索
            //相当于{query -- bool -- must -- match -- title}
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
            //高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            highlightBuilder.preTags("<span style=color:red>");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }


        //判断是否是根据分类id检索
        /*
        GET goods/_search
        {
          "query": {
            "bool": {
              "filter": [
                {
                  "term": {
                    "category3Id": "61"
                  }
                },
                {
                  "term": {
                    "tmId": "1"
                  }
                }
              ]
            }
          }
        }
         */
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }

        //品牌id
        //得到 trademark=1 ：小米 这种格式的数据
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            //使用分隔符
            String[] split = trademark.split(":");
            if(split!=null && split.length>0){
                //获取品牌id
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        //按照平台属性值进行过滤
        //数据格式  props=24：256G：机身内存  平台属性id：平台属性值名称：平台属性名称
        String[] props = searchParam.getProps();
        if(props!=null && props.length>0){
            for (String prop : props) {
                //分割字符串
                String[] split = prop.split(":");
                if(split!=null && split.length>0){
                    BoolQueryBuilder boolQuery =  QueryBuilders.boolQuery();
                    //最内层的bool
                    BoolQueryBuilder subBoolQuery =  QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    //中间的bool
                    boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));
                    //最外层的bool
                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }
        searchSourceBuilder.query(boolQueryBuilder);
        //分页
        //起始页
        int from = (searchParam.getPageNo() - 1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize());



        //排序
        //数据格式 order=1：desc  order=1：asc  order=2：desc   1：表示综合排序hotScore  2：表示按照价格排序
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            //声明一个字段
            String filed = "";
            //分割
            String[] split = order.split(":");
            if(split!=null && split.length == 2){
                switch (split[0]){
                    case "1":
                        filed = "hotScore";
                        break;
                    case "2":
                        filed = "price";
                        break;
                    default:
                }
                searchSourceBuilder.sort(filed,"asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
            }else {
                searchSourceBuilder.sort("hotScore",SortOrder.DESC);
            }
        }

        //聚合
        //品牌聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //平台属性聚合
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                )
        );

        System.out.println("dsl  = \t" + searchSourceBuilder.toString());

        //SearchRequest searchRequest = new SearchRequest("posts");官方文档，指定索引库
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        //过滤，id，defaultImg，title，price
        searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price"},null);
        return searchRequest;
    }
}
